<?php
namespace app\api\common;

/**
 * Author: aventury(aventuro@sina.com)
 * 重点内容：前后端数据传输方式
 * POST，采用JSON对象，对象，对象
 * GET，即一般的形式即可
 * 
 * Desc: 参数统一处理方法
 * 1. 防止接口被大规模调用消耗系统资源: 添加时间戳time
 * 2. 防止接口数据被黑客篡改(伪造请求)：增加校验参数mac
 * 3. 过滤time与mac, 拿到真正的请求参数
 * note: 上线或者部署到环境上才进行time与mac的校验
 * Data: 2019年8月9日
 */

use think\Request;
use think\Controller;
use think\Validate;
use think\Db;
use think\Image;
use app\api\common\ApiErrDesc;
use app\api\common\JwtAuth;
use app\api\common\ParamsArray;

class Common extends Controller {
    protected $request; // 用来处理参数
    protected $validater; // 用来校验数据 / 参数
    protected $params; // 过滤后符合要求的参数
    protected $__HTTP__; // http的协议
    protected $__DATAURL__; // 所有图片位置
    protected $wxOpenidUrl = ParamsArray::paramsWxOpenUrl;
    protected $wxappid = ParamsArray::paramsWxAppid;
    protected $wxsecret = ParamsArray::paramsWxSecret;
    protected $rules = ParamsArray::paramsArr;

    protected function _initialize() {
        parent::_initialize();
        $this->request = Request::instance();
        // ---------------------上线时，或者客户端进行了相应的参数传值，才取消注释--------------------------------------
        // $this->checked_time($this->request->only(['time']));
        // $this->check_mac($this->request->param(), $this->request->method());
        $this->params = $this->check_params($this->request->param(true));

        // php 判断http还是https
    	$http_type = ((isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on') || (isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] == 'https')) ? 'https://' : 'http://'; 
        
        // 所有图片路径
	    $this->__DATAURL__ = $http_type.$_SERVER['SERVER_NAME'].ParamsArray::paramsImgUrl;
        $this->__HTTP__ = $http_type;
    }

    /**
     * 验证请求是否超时
     * @param [array] $arr [包含时间戳的数组]
     * @return [json]      [检测结果]
     */
    public function checked_time($arr) {
        if(!isset($arr['time']) || intval($arr['time']) <=1 ) {
            $this->return_msg(ApiErrDesc::TIMEERR[0], ApiErrDesc::TIMEERR[1]);
        }
        if((time() - intval($arr['time'])) > 60) {
            $this->return_msg(ApiErrDesc::TIMEOVER[0], ApiErrDesc::TIMEOVER[1]);
        }
    }

    /**
     * api 数据返回
     * @param [int] $status [结果码 1：正常 / 0：数据问题 ]
     * @param [string] $msg [接口要返回的提示信息]
     * @param [array] $data [接口要返回的数据]
     * @return [string]      [最终的json数据]
     */
    public function return_msg($status, $msg, $data=[]) {
        $return_data['status'] = $status;
        $return_data['msg'] = $msg;
        $return_data['data'] = $data;
        echo json_encode($return_data);
        die;
    }

    /**
     * 验证mac(防止篡改数据)
     * @param [array] $arr [全部请求参数]
     * @param [string] $methods [请求类型]
     * @return [json]      [mac校验结果]
     */
    public function check_mac($arr, $methods) {
        if(!isset($arr['mac']) || empty($arr['mac'])) {
            $this->return_msg(ApiErrDesc::MACLOST[0], ApiErrDesc::MACLOST[1]);
        }
        $app_mac = $arr['mac'];
        /*****  服务器端生成mac  *****/
        unset($arr['mac']);
        unset($arr['time']);
        // 后期如果有登录校验的话，也可以考虑加入token
        // unset($arr['token']);
        $server_mac = '';

        $count = 0;
		$str = '';
		foreach($arr as $k => $v){
			$str .= substr((string)$k, 0, 1);
            if (is_string($v)) {
                $str .= (string)$v;
            } else {
                $str .= implode(",", $v);
            }
            $count++;
		}
		$str .= $count;
		if ($str !== '') {
			if(strtolower($methods) != 'post'){
				$str = urlencode($str);
			}
			$str = md5($str);
			$count = $count % 10;
			$server_mac = substr($str, 0, $count);
			$server_mac .= (string)$count;
			$server_mac .= substr($str, $count);
            $server_mac = strtoupper($server_mac);

            if($app_mac !== $server_mac){
                $this->return_msg(ApiErrDesc::MACFAIL[0], ApiErrDesc::MACFAIL[1]);
            }
        }
    }

    /**
    * 验证参数 参数过滤
    * @param  [array] $arr [只校验的需要检验的参数，像time和token不需要检验，在前面$rules有标注那些才是需要检测的参数]
    * @return [return]      [合格的参数数组]
    */
    public function check_params($arr){
        // 获取controller对应的规则
        $rule = $this->rules[$this->request->controller()][$this->request->action()];
        $this->validater = new Validate($rule);
        if (!$this->validater->check($arr)) {
            $this->return_msg(ApiErrDesc::PARAMSERR[0], $this->validater->getError());
        }

        if(isset($rule['token'])){
            // 校验必须带token参数的接口, 如token格式或者过期都会抛出异常，但不返回给前端。
            $jwtAuth = JwtAuth::getInstance();
            $jwtAuth->setToken($arr['token']);
            $jwtAuth->parserToken();
            if($jwtAuth->getExpTime() < time()){
                $this->return_msg(ApiErrDesc::LOGINEXPTIME[0], ApiErrDesc::LOGINEXPTIME[1]);
            }
            $arr['uid'] = $jwtAuth->getUserId();
        }

        // 无token,通过验证
        return $arr;
    }

    /**
     * 检测用户名并返回用户名类别
     * @param [string] $username [用户名，可能是邮箱，也可能是手机号]
     * @return [string]          [检测结果]
     */
    public function check_username($username){
        // 判断是否为邮箱
        $is_email = Validate::is($username, 'email') ? 1 : 0;
        // 判断是否为手机，phone与email组合时很好区分谁是谁
        $is_phone = preg_match('/^1((3[\d])|(4[5,6,7,9])|(5[0-3,5-9])|(6[5-7])|(7[0-8])|(8[\d])|(9[1,8,9]))\d{8}$/', $username) ? 4 : 2; 
        $flag = $is_email + $is_phone;
        switch ($flag) {
            /****  not phone not email  ****/
            case 2:
                $this->return_msg(ApiErrDesc::TELMAILER[0], ApiErrDesc::TELMAILER[1]);
                break;
            /****  is email not phone  ****/
            case 3:
                return 'email';
                break;
            /****  is phone not email  ****/
            case 4:
                return 'phone';
                break;
        }
    }

    /**
     * 检测手机号码/邮箱是否存在于数据库中，总的8种情况，有4种是需要提示的。
     * @param [string] $value [手机号码/邮箱号码]
     * @param [string] $type  [类型]
     * @param [int] $exist    [是否存在 1：是 0：否]
     * @return [string]       [返回json]
     */
    public function check_exist($value, $type, $exist) {
        $type_num  = ($type == "phone") ? 2 : 4;
        $flag      = $type_num + $exist;
        $phone_res = db('user')->where('phone', $value)->find();
        $email_res = db('user')->where('email', $value)->find();
        switch ($flag) {
        /*********** 2+0 phone need no exist  ***********/
        case 2:
            if ($phone_res) {
                $this->return_msg(ApiErrDesc::EXISTPHONE[0], ApiErrDesc::EXISTPHONE[1]);
            }
            break;
        /*********** 2+1 phone need exist  ***********/
        case 3:
            if (!$phone_res) {
                $this->return_msg(ApiErrDesc::TELNUMERR[0], ApiErrDesc::TELNUMERR[1]);
            }
            break;
        /*********** 4+0 email need no exist  ***********/
        case 4:
            if ($email_res) {
                $this->return_msg(ApiErrDesc::EXISTEMAILRR[0], ApiErrDesc::EXISTEMAILRR[1]);
            }
            break;
        /*********** 4+1 email need  exist  ***********/
        case 5:
            if (!$email_res) {
                $this->return_msg(ApiErrDesc::EMAILNUMERR[0], ApiErrDesc::EMAILNUMERR[1]);
            }
            break;
        }
    }

    /**
     * 检查验证码
     * @param [string] $username   [手机号或者邮箱号]
     * @param [int]    $code        [验证码]
     * @return [type]               [返回结果]
     */
    public function check_code($username, $code) {
        // 检测是否超时
        $last_time = session(str_replace('.', '', $username) . '_last_send_time');
        if (time() - $last_time > 120) {
            $this->return_msg(ApiErrDesc::CODETIMEOUT[0], ApiErrDesc::CODETIMEOUT[1]);
        }

        // 检测验证码是否正确
        $md5_code = md5($username . '_' . md5($code));
        if (session(str_replace('.', '', $username) . '_code') !== $md5_code) {
            $this->return_msg(ApiErrDesc::CODEERROR[0], ApiErrDesc::CODEERROR[1]);
        }

        // 不管正确与否,每个验证码只验证一次
        session(str_replace('.', '', $username) . '_code', null);
        session(str_replace('.', '', $username) . '_last_send_time', null);
    }

    /**
     * 上传文件到根目录下的uploads文件里
     * uploads下面的文件分类为(图片类型 + id) + tp生成的路径
     * @param [file]   $file  [上传的图片文件]
     * @param [string] $type  [文件夹名称]
     * @param [int]    $uid   [用户自己的id]
     * @return [json]         [返回结果]
     */
    public function upload_file($file, $type, $uid) {
        // 验证图片image
        $file->validate(['size'=>2000000000,'ext'=>'jpg,png,bmp,jpeg']);
        // 移动文件到根目录 例如移动到了: \public\uploads\avator\5\20190924\8d07efcc9c83c16f243b1b64fdbcd44d.png
        $info = $file->move(ROOT_PATH . 'public' . DS . 'uploads'."\\$type\\$uid");
        if ($info) {
            $path = "\\uploads\\$type\\$uid\\" . $info->getSaveName();
            /*  暂时不裁剪图片
            if (!empty($type)) {
                $this->image_edit($path, $type);
            }  */
            return str_replace('\\', '/', $path);
        } else {
            $this->return_msg(ApiErrDesc::ERROR[0], $file->getError());
        }
    }

    /**
     * 裁剪图片$type=avator  200*200
     * @param [string]   $path  [上传图片的路径]
     * @param [string] $type   [剪切规格]
     * @return [json]         [返回结果]
     */
    public function image_edit($path, $type) {
        $image = Image::open(ROOT_PATH . 'public' . $path);
        switch ($type) {
            case 'avator':
                $image->thumb(200, 200, Image::THUMB_CENTER)->save(ROOT_PATH . 'public' . $path);
                break;
        }
    }

    /**
     * 重要的个人信息隐藏（手机号码）
     * @param   [sting]   $telnum  [传递过来的手机号码]
     * @return  [string]  替换过后的加密过的手机号码
     */
    public function encrypt_info($telnum){
        return substr_replace($telnum, '****',3,4);
    }

    /**
     * 公共方法，取出集合中的某个字段
     * @param $input      传入的值
     * @param $columnKey  取出的字段
     * @return            返回相应的数组
     */
    public function i_array_column($input, $columnKey, $indexKey=null){
        if(!function_exists('array_column')){ 
            $columnKeyIsNumber  = (is_numeric($columnKey))?true:false; 
            $indexKeyIsNull            = (is_null($indexKey))?true :false; 
            $indexKeyIsNumber     = (is_numeric($indexKey))?true:false; 
            $result                         = array(); 
            foreach((array)$input as $key=>$row){ 
                if($columnKeyIsNumber){ 
                    $tmp= array_slice($row, $columnKey, 1); 
                    $tmp= (is_array($tmp) && !empty($tmp))?current($tmp):null; 
                }else{ 
                    $tmp= isset($row[$columnKey])?$row[$columnKey]:null; 
                } 
                if(!$indexKeyIsNull){ 
                    if($indexKeyIsNumber){ 
                      $key = array_slice($row, $indexKey, 1); 
                      $key = (is_array($key) && !empty($key))?current($key):null; 
                      $key = is_null($key)?0:$key; 
                    }else{ 
                      $key = isset($row[$indexKey])?$row[$indexKey]:0; 
                    } 
                } 
                $result[$key] = $tmp; 
            } 
            return $result; 
        }else{
            return array_column($input, $columnKey, $indexKey);
        } 
    }
}